﻿using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using UIOC.CodeAnalysis.Extensions;
using UIOC.CodeAnalysis.Generators;
using UIOC.CodeAnalysis.Helpers;

namespace UIOC.Observable.CodeAnalysis.Generators;

internal static class ModelGenerator {

    public readonly struct GeneratedInfo(GeneratedSource source) {

        public GeneratedSource GeneratedSource { get; } = source;
    }

    private static void LogInfo(string message) {
        SourceOutputHelper.LogInfo(DiagnosticSeverity.Info, $"[{nameof(ModelGenerator)}] {message}");
    }

    public static ImmutableArray<GeneratedInfo> GenerateModel(
        Compilation compilation,
        ImmutableArray<ClassDeclarationSyntax> classDeclarationSyntaxes
    ) {
        var observableModelInterfaceSymbol = compilation.GetTypeByMetadataName(TypeFullNames.IObservableModel);
        if (observableModelInterfaceSymbol == null) {
            return ImmutableArray<GeneratedInfo>.Empty;
        }

        var modelClassSymbol = compilation.GetTypeByMetadataName(TypeFullNames.Model);
        if (modelClassSymbol == null) {
            return ImmutableArray<GeneratedInfo>.Empty;
        }

        var observableObjectInterfaceSymbol = compilation.GetTypeByMetadataName(TypeFullNames.IObservableObject);
        if (observableObjectInterfaceSymbol == null) {
            return ImmutableArray<GeneratedInfo>.Empty;
        }

        LogInfo($"[Assembly({compilation.Assembly.Name})] {nameof(GenerateModel)} ...");

        var result = ImmutableArray.CreateBuilder<GeneratedInfo>();

        var classSymbols = classDeclarationSyntaxes
            .Select(syntax => compilation.GetSemanticModel(syntax.SyntaxTree).GetDeclaredSymbol(syntax))
            .OfType<INamedTypeSymbol>()
            .Distinct(SymbolEqualityComparer.Default)
            .Cast<INamedTypeSymbol>()
            .ToList();

        foreach (var classSymbol in classSymbols) {
            //var classFullyQualifiedName = classSymbol.ToDisplayString(new SymbolDisplayFormat(
            //    globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
            //    typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            //    genericsOptions: SymbolDisplayGenericsOptions.None,
            //    miscellaneousOptions:
            //        SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers |
            //        SymbolDisplayMiscellaneousOptions.UseSpecialTypes
            //));
            //var metaClassSymbol = compilation.GetTypeByMetadataName(classFullyQualifiedName);
            //if (metaClassSymbol != null) {
            //    classSymbol = metaClassSymbol;
            //}
            //LogInfo($"{nameof(classSymbol)}={classSymbol}, {nameof(classFullyQualifiedName)}={classFullyQualifiedName}");
            var (compilationUnitSyntax, namespaceName) = GenerateModelCompilationUnit(
                classSymbol,
                observableModelInterfaceSymbol,
                modelClassSymbol,
                observableObjectInterfaceSymbol
            );
            if (compilationUnitSyntax == null) {
                continue;
            }
            var outputSource = SourceText.From(compilationUnitSyntax.NormalizeWhitespace().ToFullString(), Encoding.UTF8);
            var hintName = classSymbol.GetShortName().Replace("<", "(").Replace(">", ")");
            result.Add(new GeneratedInfo(
                new GeneratedSource(
                    hintName: hintName,
                    assemblyName: compilation.Assembly.Name,
                    namespaceName: namespaceName,
                    sourceText: outputSource,
                    compilationUnitSyntax: compilationUnitSyntax,
                    generatorName: nameof(ImplementationGenerator)
                )
            ));
        }
        return result.ToImmutable();
    }

    private static (CompilationUnitSyntax CompilationUnitSyntax, string NamespaceName) GenerateModelCompilationUnit(
        INamedTypeSymbol classSymbol,
        INamedTypeSymbol observableModelInterfaceSymbol,
        INamedTypeSymbol modelClassSymbol,
        INamedTypeSymbol observableObjectInterfaceSymbol
    ) {
        if (!classSymbol.ContainsInterface(observableModelInterfaceSymbol)) {
            return (null, null);
        }

        if (SymbolEqualityComparer.Default.Equals(classSymbol, modelClassSymbol)) {
            return (null, null);
        }

        var className = classSymbol.Name;

        var modifierSyntaxKind = classSymbol.DeclaredAccessibility.ToSyntaxKind();
        var modifierSyntaxTokens = new SyntaxToken[modifierSyntaxKind == null ? 1 : 2];
        modifierSyntaxTokens[modifierSyntaxTokens.Length - 1] = SyntaxFactory.Token(SyntaxKind.PartialKeyword);
        if (modifierSyntaxKind != null) {
            modifierSyntaxTokens[0] = SyntaxFactory.Token(modifierSyntaxKind.Value);
        }

        var classDeclarationSyntax = SyntaxFactory.ClassDeclaration(className).AddModifiers(modifierSyntaxTokens);
        var allocateMethodName = $"Internal__Model_{MemberNames.Model.Allocate}__";
        var releaseMethodName = $"Internal__Model_{MemberNames.Model.Release}__";
        classDeclarationSyntax = classDeclarationSyntax.AddMembers(
            GenerateMethod(allocateMethodName, classSymbol, observableObjectInterfaceSymbol, true),
            GenerateMethod(releaseMethodName, classSymbol, observableObjectInterfaceSymbol, false)
        );

        var leadingTriviaSyntax = SyntaxFactory.Comment(UIOCObservableGenerator.Leading);
        if (classSymbol.ContainingNamespace.IsGlobalNamespace) {
            // 全局命名空间
            classDeclarationSyntax = classDeclarationSyntax.WithLeadingTrivia(leadingTriviaSyntax, SyntaxFactory.CarriageReturnLineFeed);
            return (SyntaxFactory.CompilationUnit().AddMembers(classDeclarationSyntax), null);
        } else {
            // 非全局命名空间
            var namespaceName = classSymbol.ContainingNamespace.GetFullNamespaceName();
            var namespaceDeclarationSyntax = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName(namespaceName))
                .WithLeadingTrivia(leadingTriviaSyntax, SyntaxFactory.CarriageReturnLineFeed)
                .WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed);
            namespaceDeclarationSyntax = namespaceDeclarationSyntax.AddMembers(classDeclarationSyntax);
            return (SyntaxFactory.CompilationUnit().AddMembers(namespaceDeclarationSyntax), namespaceName);
        }
    }

    private static ExpressionStatementSyntax GenerateEventSubscription(string identifierName, bool isAddOrRemove) {
        var subscriptionExpression = SyntaxFactory.AssignmentExpression(
            kind: isAddOrRemove ? SyntaxKind.AddAssignmentExpression : SyntaxKind.SubtractAssignmentExpression,
            left: SyntaxFactory.MemberAccessExpression(
                kind: SyntaxKind.SimpleMemberAccessExpression,
                expression: SyntaxFactory.IdentifierName(identifierName),
                operatorToken: SyntaxFactory.Token(SyntaxKind.DotToken),
                name: SyntaxFactory.IdentifierName(MemberNames.IObservableObject.Changed)
            ),
            right: SyntaxFactory.IdentifierName(MemberNames.IObservableObject.NotifyChanged)
        );
        return SyntaxFactory.ExpressionStatement(subscriptionExpression);
    }

    private static MethodDeclarationSyntax GenerateMethod(
        string methodName,
        INamedTypeSymbol classSymbol,
        INamedTypeSymbol observableObjectInterfaceSymbol,
        bool isAddOrRemove
    ) {
        var statementSyntaxes = UIOC.CodeAnalysis.Generators.AutowiredGenerator.GenerateBaseInvokeExpressionStatement(methodName);
        var statementList = new List<StatementSyntax>();
        var propertySymbols = classSymbol.GetMembers().OfType<IPropertySymbol>()
            .Where(property => property.GetMethod?.IsImplicitlyDeclared ?? false)
            .Where(property => property.Type.ContainsInterface(observableObjectInterfaceSymbol));
        foreach (var propertySymbol in propertySymbols) {
            statementList.Add(GenerateEventSubscription(propertySymbol.Name, isAddOrRemove));
        }
        var fieldSymbols = classSymbol.GetMembers().OfType<IFieldSymbol>()
            .Where(field => field.Type.ContainsInterface(observableObjectInterfaceSymbol));
        foreach (var fieldSymbol in fieldSymbols) {
            statementList.Add(GenerateEventSubscription(fieldSymbol.Name, isAddOrRemove));
        }
        if (isAddOrRemove) {
            statementList.Insert(0, statementSyntaxes);
        } else {
            statementList.Add(statementSyntaxes);
        }
        return UIOC.CodeAnalysis.Generators.AutowiredGenerator.GenerateOverrideMethodDeclaration(
            methodName, statementList
        );
    }
}
